#include "graphics.h"

#include "graphics/bg_main.h"
#include "graphics/sprite_main_palette.h"
#include "graphics/sprite_main_buttons_neutral_1.h"
#include "graphics/sprite_main_buttons_neutral_2.h"
#include "graphics/sprite_main_bar.h"
#include "graphics/sprite_main_highlights.h"
#include "graphics/sprite_main_indicators.h"

#include "graphics/bg_sub_palette.h"
#include "graphics/bg_sub_topscreen.h"
#include "graphics/sprite_sub_palette.h"
#include "graphics/sprite_sub_timer_digits.h"
#include "graphics/sprite_sub_info_chars.h"
#include "graphics/sprite_sub_wifi_symbols.h"

#include "unicode.h"
#include "unifont.h"

#include <nds.h>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <fstream>

Unifont unifont;

// ---------------------------------------------------------------------------
void Graphics::init()
// ---------------------------------------------------------------------------
{
	lcdMainOnBottom();

	// set up the main screen (bottom)
	REG_DISPCNT = MODE_0_2D | DISPLAY_BG0_ACTIVE;

	VRAM_E_CR = VRAM_E_MAIN_BG | VRAM_ENABLE;
	REG_BG0CNT = BG_32x32 | BG_COLOR_256 | BG_MAP_BASE(0) | BG_TILE_BASE(3) | BG_PRIORITY_3;
	swiCopy(bg_mainPal, BG_PALETTE, bg_mainPalLen);
	swiCopy(bg_mainTiles, BG_TILE_RAM(3), bg_mainTilesLen);
	swiCopy(bg_mainMap, BG_MAP_RAM(0), bg_mainMapLen);

	VRAM_A_CR = VRAM_A_MAIN_SPRITE | VRAM_ENABLE;
	oamInit(&oamMain, SpriteMapping_1D_32, false);
	swiCopy(sprite_main_palette, SPRITE_PALETTE, sprite_main_paletteLen);
	main_buttons_neutral_1_addr = SPRITE_GFX;
	swiCopy(sprite_main_buttons_neutral_1Tiles, main_buttons_neutral_1_addr, sprite_main_buttons_neutral_1TilesLen);
	main_buttons_neutral_2_addr = main_buttons_neutral_1_addr + (sprite_main_buttons_neutral_1TilesLen >> 1);
	swiCopy(sprite_main_buttons_neutral_2Tiles, main_buttons_neutral_2_addr, sprite_main_buttons_neutral_2TilesLen);
	main_highlights_addr = main_buttons_neutral_2_addr + (sprite_main_buttons_neutral_2TilesLen >> 1);
	swiCopy(sprite_main_highlightsTiles, main_highlights_addr, sprite_main_highlightsTilesLen);
	main_indicators_addr = main_highlights_addr + (sprite_main_highlightsTilesLen >> 1);
	swiCopy(sprite_main_indicatorsTiles, main_indicators_addr, sprite_main_indicatorsTilesLen);
	main_bar_addr = main_indicators_addr + (sprite_main_indicatorsTilesLen >> 1);	
	swiCopy(sprite_main_barTiles, main_bar_addr, sprite_main_barTilesLen);
	main_glyphs_addr = main_bar_addr + (sprite_main_barTilesLen >> 1);

	oamSet(&oamMain, 0, 28,  4, 2, 0, SpriteSize_32x16, SpriteColorFormat_256Color, main_bar_addr, -1, false, false, false, false, false);
	oamSet(&oamMain, 1,  3,  0, 1, 0, SpriteSize_64x32, SpriteColorFormat_256Color, main_buttons_neutral_1_addr, -1, false, false, false, false, false);
	oamSet(&oamMain, 2, 67,  0, 1, 0, SpriteSize_32x32, SpriteColorFormat_256Color, main_buttons_neutral_2_addr, -1, false, false, false, false, false);
	oamUpdate(&oamMain);

	// set up the sub-screen (top)
	REG_DISPCNT_SUB = MODE_0_2D | DISPLAY_BG0_ACTIVE;
	
	VRAM_C_CR = VRAM_C_SUB_BG | VRAM_ENABLE;
	REG_BG0CNT_SUB = BG_32x32 | BG_COLOR_256 | BG_MAP_BASE(0) | BG_TILE_BASE(3);
	swiCopy(bg_sub_palette, BG_PALETTE_SUB, bg_sub_paletteLen);
	swiCopy(bg_sub_topscreenTiles, BG_TILE_RAM_SUB(3), bg_sub_topscreenTilesLen);
	swiCopy(bg_sub_topscreenMap, BG_MAP_RAM_SUB(0), bg_sub_topscreenMapLen);
	
	VRAM_D_CR = VRAM_D_SUB_SPRITE | VRAM_ENABLE;
	oamInit(&oamSub, SpriteMapping_1D_32, false);
	swiCopy(sprite_sub_palette, SPRITE_PALETTE_SUB, sprite_sub_paletteLen);
	sub_timer_digits_addr = SPRITE_GFX_SUB;
	swiCopy(sprite_sub_timer_digitsTiles, sub_timer_digits_addr, sprite_sub_timer_digitsTilesLen);
	sub_info_chars_addr = (sub_timer_digits_addr + (sprite_sub_timer_digitsTilesLen >> 1));
	swiCopy(sprite_sub_info_charsTiles, sub_info_chars_addr, sprite_sub_info_charsTilesLen);
	sub_wifi_symbols_addr = (sub_info_chars_addr + (sprite_sub_info_charsTilesLen >> 1));
	swiCopy(sprite_sub_wifi_symbolsTiles, sub_wifi_symbols_addr, sprite_sub_wifi_symbolsTilesLen);
	sub_glyphs_addr = (sub_wifi_symbols_addr + (sprite_sub_wifi_symbolsTilesLen >> 1));
	oamUpdate(&oamSub);

	setWiFi(0);
	fileTime time;
	time.curmin = time.cursec = time.min = time.sec = time.now = time.total = 0;
	setTimer(time);
}

// ---------------------------------------------------------------------------
void Graphics::toggleLoading(bool on)
// ---------------------------------------------------------------------------
{
	if (on)
	{
		oamSet(&oamMain, 5, 112, 88, 0, 0, SpriteSize_32x16, SpriteColorFormat_256Color, main_bar_addr+(1<<8), -1, false, false, false, false, false);
	}
	else
	{
		oamSet(&oamMain, 5, 112, 88, 0, 0, SpriteSize_32x16, SpriteColorFormat_256Color, main_bar_addr+(1<<8), -1, false, true, false, false, false);
	}
	oamUpdate(&oamMain);
}

// ---------------------------------------------------------------------------
void Graphics::setButtonHighlight(Graphics::button button, bool on)
// ---------------------------------------------------------------------------
{
	if (on)
	{
		switch (button)
		{
			case BUTTON_PLAY:
				oamSet(&oamMain, 3, 3, 0, 0, 0, SpriteSize_32x32, SpriteColorFormat_256Color, main_highlights_addr+(0<<9), -1, false, false, false, false, false);
			break;
			case BUTTON_PAUSE:
				oamSet(&oamMain, 3, 3, 0, 0, 0, SpriteSize_32x32, SpriteColorFormat_256Color, main_highlights_addr+(2<<9), -1, false, false, false, false, false);
			break;
			case BUTTON_STOP:
				oamSet(&oamMain, 3, 33, 13, 0, 0, SpriteSize_32x32, SpriteColorFormat_256Color, main_highlights_addr+(3<<9), -1, false, false, false, false, false);
			break;
			case BUTTON_PREV:
				oamSet(&oamMain, 3, 51, 13, 0, 0, SpriteSize_32x32, SpriteColorFormat_256Color, main_highlights_addr+(4<<9), -1, false, false, false, false, false);
			break;
			case BUTTON_NEXT:
				oamSet(&oamMain, 3, 69, 13, 0, 0, SpriteSize_32x32, SpriteColorFormat_256Color, main_highlights_addr+(5<<9), -1, false, false, false, false, false);
			break;
			case BUTTON_SWITCH:
				oamSet(&oamMain, 3, 220, 15, 0, 0, SpriteSize_32x32, SpriteColorFormat_256Color, main_highlights_addr+(6<<9), -1, false, false, false, false, false);
			break;
		}
	}
	else
	{
		switch (button)
		{
			case BUTTON_PAUSE:
				oamSet(&oamMain, 3, 3, 0, 0, 0, SpriteSize_32x32, SpriteColorFormat_256Color, main_highlights_addr+(1<<9), -1, false, false, false, false, false);
			break;
			default:
			oamSet(&oamMain, 3, 3, 0, 0, 0, SpriteSize_32x32, SpriteColorFormat_256Color, main_highlights_addr, -1, false, true, false, false, false);
		}
	}
	oamUpdate(&oamMain);
}

// ---------------------------------------------------------------------------
void Graphics::setTimer(fileTime time)
// ---------------------------------------------------------------------------
{
	oamSet(&oamSub, 0,  17, 130, 0, 0, SpriteSize_16x32, SpriteColorFormat_256Color, sub_timer_digits_addr+ ((int)(floor(time.curmin/100)) << 8), -1, false, false, false, false, false);
	oamSet(&oamSub, 1,  36, 130, 0, 0, SpriteSize_16x32, SpriteColorFormat_256Color, sub_timer_digits_addr+ ((int)(floor(time.curmin/ 10)) << 8), -1, false, false, false, false, false);
	oamSet(&oamSub, 2,  55, 130, 0, 0, SpriteSize_16x32, SpriteColorFormat_256Color, sub_timer_digits_addr+ ((int)      (time.curmin % 10) << 8), -1, false, false, false, false, false);
	oamSet(&oamSub, 3,  82, 130, 0, 0, SpriteSize_16x32, SpriteColorFormat_256Color, sub_timer_digits_addr+ ((int)(floor(time.cursec/ 10)) << 8), -1, false, false, false, false, false);
	oamSet(&oamSub, 4, 101, 130, 0, 0, SpriteSize_16x32, SpriteColorFormat_256Color, sub_timer_digits_addr+ ((int)      (time.cursec % 10) << 8), -1, false, false, false, false, false);
	oamSet(&oamSub, 5, 139, 130, 0, 0, SpriteSize_16x32, SpriteColorFormat_256Color, sub_timer_digits_addr+ ((int)   (floor(time.min/100)) << 8), -1, false, false, false, false, false);
	oamSet(&oamSub, 6, 158, 130, 0, 0, SpriteSize_16x32, SpriteColorFormat_256Color, sub_timer_digits_addr+ ((int)   (floor(time.min/ 10)) << 8), -1, false, false, false, false, false);
	oamSet(&oamSub, 7, 177, 130, 0, 0, SpriteSize_16x32, SpriteColorFormat_256Color, sub_timer_digits_addr+ ((int)         (time.min % 10) << 8), -1, false, false, false, false, false);
	oamSet(&oamSub, 8, 204, 130, 0, 0, SpriteSize_16x32, SpriteColorFormat_256Color, sub_timer_digits_addr+ ((int)    (floor(time.sec/10)) << 8), -1, false, false, false, false, false);
	oamSet(&oamSub, 9, 223, 130, 0, 0, SpriteSize_16x32, SpriteColorFormat_256Color, sub_timer_digits_addr+ ((int)         (time.sec % 10) << 8), -1, false, false, false, false, false);
	oamUpdate(&oamSub);
	u8 offset = (198*time.now)/time.total;
	if (time.total == 0) offset = 0;
	//28
	oamSet(&oamMain, 0, 28+offset, 4, 2, 0, SpriteSize_32x16, SpriteColorFormat_256Color, main_bar_addr, -1, false, false, false, false, false);
	oamUpdate(&oamMain);
}

// ---------------------------------------------------------------------------
void Graphics::setWiFi(u8 status)
// ---------------------------------------------------------------------------
{
	oamSet(&oamSub, 10, 243, 178, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_wifi_symbols_addr+(status << 7), -1, false, false, false, false, false);
	oamUpdate(&oamSub);
}

// ---------------------------------------------------------------------------
void Graphics::setInfo(fileInfo info)
// ---------------------------------------------------------------------------
{
	char tmpChr[6];
	
	// sample rate
	memset(tmpChr, 0, 6);
	sprintf(tmpChr, "%05u", info.sampleRate);
	oamSet(&oamSub, 11, 112, 2, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(tmpChr[0]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 12, 123, 2, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(tmpChr[1]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 13, 134, 2, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(tmpChr[2]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 14, 145, 2, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(tmpChr[3]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 15, 156, 2, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(tmpChr[4]) << 7), -1, false, false, false, false, false);
	
	// codec
	oamSet(&oamSub, 16, 211, 2, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(info.type[0]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 17, 222, 2, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(info.type[1]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 18, 233, 2, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(info.type[2]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 19, 244, 2, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(info.type[3]) << 7), -1, false, false, false, false, false);
	
	// bits
	memset(tmpChr, 0, 6);
	sprintf(tmpChr, "%02u", info.bits);
	oamSet(&oamSub, 20, 121, 19, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(tmpChr[0]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 21, 132, 19, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(tmpChr[1]) << 7), -1, false, false, false, false, false);
	
	// kbps
	memset(tmpChr, 0, 6);
	sprintf(tmpChr, "%04u", info.kbps);
	oamSet(&oamSub, 22, 180, 19, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(tmpChr[0]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 23, 191, 19, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(tmpChr[1]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 24, 202, 19, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(tmpChr[2]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 25, 213, 19, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(tmpChr[3]) << 7), -1, false, false, false, false, false);
	
	// channels
	memset(tmpChr, 0, 6);
	sprintf(tmpChr, "%01u", info.channels);
	oamSet(&oamSub, 26, 244, 19, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(tmpChr[0]) << 7), -1, false, false, false, false, false);
	
	// year
	oamSet(&oamSub, 27, 155, 57, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(info.year[0]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 28, 166, 57, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(info.year[1]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 29, 177, 57, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(info.year[2]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 30, 188, 57, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(info.year[3]) << 7), -1, false, false, false, false, false);
	
	// track
	oamSet(&oamSub, 31, 155, 88, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(info.track[0]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 32, 166, 88, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(info.track[1]) << 7), -1, false, false, false, false, false);
	
	// total tracks
	oamSet(&oamSub, 33, 185, 88, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(info.alltracks[0]) << 7), -1, false, false, false, false, false);
	oamSet(&oamSub, 34, 196, 88, 0, 0, SpriteSize_16x16, SpriteColorFormat_256Color, sub_info_chars_addr+(mapChar(info.alltracks[1]) << 7), -1, false, false, false, false, false);
	
	oamUpdate(&oamSub);
}

// ---------------------------------------------------------------------------
u8 Graphics::mapChar(char index)
// ---------------------------------------------------------------------------
{
	if (0x30 <= index && index <= 0x39) {
		return index-48;
	}
	else if (0x41 <= index && index <= 0x5A) {
		return index-55;
	}
	else if (0x61 <= index && index <= 0x7A) {
		return index-87;
	} else {
		return 0;
	}
}

// ---------------------------------------------------------------------------
void Graphics::setTags(fileInfo info)
// ---------------------------------------------------------------------------
{
	toggleLoading(true);
	// clear tags
	for (u8 i=35; i<128; ++i)
	{
		oamSet(&oamSub, i, 0, 0, 0, 0, SpriteSize_32x16, SpriteColorFormat_256Color, 0, 0, false, true, false, false, false);
	}
	
	// vectors for line codepoints
	std::vector<u32> artistCP;
	std::vector<u32> titleCP;
	std::vector<u32> albumCP;
	// table codepoint - glyph data
	GlyphTable glyphs;
	
	if (info.artist.size() == 0)
	{
		info.artist = "=No data=";
	}
	if (info.title.size() == 0)
	{
		info.title = "=No data=";
	}
	if (info.album.size() == 0)
	{
		info.album = "=No data=";
	}
	
	// decode UTF-8 artist string
	Unicode::utf8decode((unsigned char*)info.artist.data(), info.artist.size(), &artistCP);
	for (u8 i=0; i<artistCP.size(); ++i) {
		glyphs.addCP( artistCP[i] );
	}
	
	// decode UTF-8 title string
	Unicode::utf8decode((unsigned char*)info.title.data(), info.title.size(), &titleCP);
	for (u8 i=0; i<titleCP.size(); ++i) {
		glyphs.addCP( titleCP[i] );
	}
	
	// decode UTF-8 album string
	Unicode::utf8decode((unsigned char*)info.album.data(), info.album.size(), &albumCP);
	for (u8 i=0; i<albumCP.size(); ++i) {
		glyphs.addCP( albumCP[i] );
	}
	
	// sort the table
	glyphs.sort();
	
	// read the glyph data
	unifont.read( &glyphs.glyphs );
	
	std::string nullglyph = "00000000000000000000000000000000";
	std::string tempstring;
	std::string nextglyph;
	bool niu = true;
	unsigned char glyph_total[512];
	u16* glyphAddr = sub_glyphs_addr;
	u8 sprite = 35; // 35+
	u8 glyphsW = 0;
	u8 offset = 0;
	
	// artist
	for (u8 i=0; i<artistCP.size(); ++i)
	{
		if (niu)
		{
			tempstring = glyphs.getGlyphHex( artistCP[i] );
			if (tempstring.size() == 64)
			{
				tempstring = glyph64( tempstring );
			}
			niu = false;
		}
		else
		{
			tempstring = addGlyphs( tempstring, glyphs.getGlyphHex( artistCP[i] ) );
			nextglyph = glyphs.getGlyphHex( artistCP[i+1] );
			
			if ( tempstring.size() == 96 && nextglyph.size() == 64 )
			{
				tempstring = addGlyphs( tempstring, nullglyph );
				if (glyphsW != 0)
				{
					offset = 8;
				}
			}
			
			if (tempstring.size() == 128)
			{
				hex2glyph(tempstring.data(), glyph_total, 128);
				swiCopy(glyph_total, glyphAddr, 512);
				oamSet(&oamSub, sprite, 0+glyphsW, 40, 0, 0, SpriteSize_32x16, SpriteColorFormat_256Color, glyphAddr, -1, false, false, false, false, false);
				glyphAddr = (glyphAddr + 256);
				++sprite;
				niu = true;
				glyphsW += 32 - offset;
				offset = 0;
			}
		}
	}

	if (tempstring.size() < 128)
	{
		while (tempstring.size() < 128)
		{
			tempstring = addGlyphs( tempstring, nullglyph );
		}
		hex2glyph(tempstring.data(), glyph_total, 128);
		swiCopy(glyph_total, glyphAddr, 512);
		oamSet(&oamSub, sprite, 0+glyphsW, 40, 0, 0, SpriteSize_32x16, SpriteColorFormat_256Color, glyphAddr, -1, false, false, false, false, false);
		glyphAddr = (glyphAddr + 256);
		++sprite;
	}
	
	// title
	glyphsW = 0;
	niu = true;
	for (u8 i=0; i<titleCP.size(); ++i)
	{
		if (niu)
		{
			tempstring = glyphs.getGlyphHex( titleCP[i] );
			if (tempstring.size() == 64)
			{
				tempstring = glyph64( tempstring );
			}
			niu = false;
		}
		else
		{
			tempstring = addGlyphs( tempstring, glyphs.getGlyphHex( titleCP[i] ) );
			nextglyph = glyphs.getGlyphHex( titleCP[i+1] );
			
			if ( tempstring.size() == 96 && nextglyph.size() == 64 )
			{
				tempstring = addGlyphs( tempstring, nullglyph );
				if (glyphsW != 0)
				{
					offset = 8;
				}
			}
			
			if (tempstring.size() == 128)
			{
				hex2glyph(tempstring.data(), glyph_total, 128);
				swiCopy(glyph_total, glyphAddr, 512);
				oamSet(&oamSub, sprite, 0+glyphsW, 71, 0, 0, SpriteSize_32x16, SpriteColorFormat_256Color, glyphAddr, -1, false, false, false, false, false);
				glyphAddr = (glyphAddr + 256);
				++sprite;
				niu = true;
				glyphsW += 32 - offset;
				offset = 0;
			}
		}
	}

	if (tempstring.size() < 128)
	{
		while (tempstring.size() < 128)
		{
			tempstring = addGlyphs( tempstring, nullglyph );
		}
		hex2glyph(tempstring.data(), glyph_total, 128);
		swiCopy(glyph_total, glyphAddr, 512);
		oamSet(&oamSub, sprite, 0+glyphsW, 71, 0, 0, SpriteSize_32x16, SpriteColorFormat_256Color, glyphAddr, -1, false, false, false, false, false);
		glyphAddr = (glyphAddr + 256);
		++sprite;
	}
	
	// album
	glyphsW = 0;
	niu = true;
	for (u8 i=0; i<albumCP.size(); ++i)
	{
		if (niu)
		{
			tempstring = glyphs.getGlyphHex( albumCP[i] );
			if (tempstring.size() == 64)
			{
				tempstring = glyph64( tempstring );
			}
			niu = false;
		}
		else
		{
			tempstring = addGlyphs( tempstring, glyphs.getGlyphHex( albumCP[i] ) );
			nextglyph = glyphs.getGlyphHex( albumCP[i+1] );
			
			if ( tempstring.size() == 96 && nextglyph.size() == 64 )
			{
				tempstring = addGlyphs( tempstring, nullglyph );
				if (glyphsW != 0)
				{
					offset = 8;
				}
			}
			
			if (tempstring.size() == 128)
			{
				hex2glyph(tempstring.data(), glyph_total, 128);
				swiCopy(glyph_total, glyphAddr, 512);
				oamSet(&oamSub, sprite, 0+glyphsW, 102, 0, 0, SpriteSize_32x16, SpriteColorFormat_256Color, glyphAddr, -1, false, false, false, false, false);
				glyphAddr = (glyphAddr + 256);
				++sprite;
				niu = true;
				glyphsW += 32 - offset;
				offset = 0;
			}
		}
	}

	if (tempstring.size() < 128)
	{
		while (tempstring.size() < 128)
		{
			tempstring = addGlyphs( tempstring, nullglyph );
		}
		hex2glyph(tempstring.data(), glyph_total, 128);
		swiCopy(glyph_total, glyphAddr, 512);
		oamSet(&oamSub, sprite, 0+glyphsW, 102, 0, 0, SpriteSize_32x16, SpriteColorFormat_256Color, glyphAddr, -1, false, false, false, false, false);
		glyphAddr = (glyphAddr + 256);
		++sprite;
	}

	oamUpdate(&oamSub);
	toggleLoading(false);
}

// ---------------------------------------------------------------------------
void Graphics::setBrowserLines(u16 start, std::vector<std::string> *list)
// ---------------------------------------------------------------------------
{
	toggleLoading(true);
	oamSet(&oamMain, 4, 173, 19, 0, 0, SpriteSize_64x32, SpriteColorFormat_256Color, main_indicators_addr+(1<<10), -1, false, false, false, false, false);
	oamUpdate(&oamMain);

	u16 nlines = list->size() - start;
	if (nlines > 10) nlines = 10;
	std::vector<u32> lines[nlines];
	for (u16 i=0; i<nlines; ++i)
	{
		Unicode::utf8decode((unsigned char*)list->at(i).data(), list->at(i).size(), &lines[i]);
	}
	setBrowserLinesReal(lines, nlines);
	toggleLoading(false);
}

// ---------------------------------------------------------------------------
void Graphics::setBrowserLines(u16 start, std::vector<fileInfo> *list)
// ---------------------------------------------------------------------------
{
	toggleLoading(true);
	oamSet(&oamMain, 4, 127, 19, 0, 0, SpriteSize_64x32, SpriteColorFormat_256Color, main_indicators_addr, -1, false, false, false, false, false);
	oamUpdate(&oamMain);

	u16 nlines = list->size() - start;
	if (nlines > 10) nlines = 10;
	std::vector<u32> lines[nlines];
	for (u16 i=0; i<nlines; ++i)
	{
		Unicode::utf8decode((unsigned char*)list->at(i).title.data(), list->at(i).title.size(), &lines[i]);
	}
	setBrowserLinesReal(lines, nlines);
	toggleLoading(false);
}

// ---------------------------------------------------------------------------
void Graphics::setBrowserLinesReal(std::vector<u32> *lines, u8 nlines)
// ---------------------------------------------------------------------------
{
	// clear
	u8 sprite = 10;
	for (u8 i=sprite; i<128; ++i)
	{
		oamSet(&oamMain, i, 0, 0, 0, 0, SpriteSize_32x16, SpriteColorFormat_256Color, 0, 0, false, true, false, false, false);
	}
	if (nlines == 0) return;
	// table codepoint - glyph data
	GlyphTable glyphs;
	// decode UTF-8 string
	for (u8 i=0; i<nlines; ++i)
	{
		for (u8 j=0; j<lines[i].size(); ++j) glyphs.addCP( lines[i][j] );
	}
	// sort the table
	glyphs.sort();
	// read the glyph data
	unifont.read( &glyphs.glyphs );

	std::string nullglyph = "00000000000000000000000000000000";
	std::string tempstring;
	std::string nextglyph;
	bool niu = true;
	unsigned char glyph_total[512];
	u16* glyphAddr = main_glyphs_addr;
	u8 glyphsW = 0;
	u8 offset = 0;
	
	for (u16 n=0; n<nlines; ++n)
	{
		glyphsW = 0;
		niu = true;
		for (u8 i=0; i<lines[n].size(); ++i)
		{
			if (niu)
			{
				tempstring = glyphs.getGlyphHex( lines[n][i] );
				if (tempstring.size() == 64)
				{
					tempstring = glyph64( tempstring );
				}
				niu = false;
			}
			else
			{
				tempstring = addGlyphs( tempstring, glyphs.getGlyphHex( lines[n][i] ) );
				nextglyph = glyphs.getGlyphHex( lines[n][i+1] );
				
				if ( tempstring.size() == 96 && nextglyph.size() == 64 )
				{
					tempstring = addGlyphs( tempstring, nullglyph );
					if (glyphsW != 0)
					{
						offset = 8;
					}
				}
				
				if (tempstring.size() == 128)
				{
					hex2glyph(tempstring.data(), glyph_total, 128);
					swiCopy(glyph_total, glyphAddr, 512);
					oamSet(&oamMain, sprite, glyphsW, ((n+2)<<4), 0, 0, SpriteSize_32x16, SpriteColorFormat_256Color, glyphAddr, -1, false, false, false, false, false);
					glyphAddr = (glyphAddr + 256);
					++sprite;
					niu = true;
					glyphsW += 32 - offset;
					offset = 0;
				}
			}
		}
		if (tempstring.size() < 128)
		{
			while (tempstring.size() < 128)
			{
				tempstring = addGlyphs( tempstring, nullglyph );
			}
			hex2glyph(tempstring.data(), glyph_total, 128);
			swiCopy(glyph_total, glyphAddr, 512);
			oamSet(&oamMain, sprite, glyphsW, ((n+2)<<4), 0, 0, SpriteSize_32x16, SpriteColorFormat_256Color, glyphAddr, -1, false, false, false, false, false);
			glyphAddr = (glyphAddr + 256);
			++sprite;
		}
	}

	oamUpdate(&oamMain);
}

// ---------------------------------------------------------------------------
void Graphics::setBrowserHighlight(u8 lineno)
// ---------------------------------------------------------------------------
{
}

// ---------------------------------------------------------------------------
void Graphics::hex2glyph(const char* string, unsigned char* out, u8 len)
// ---------------------------------------------------------------------------
{
	for (u8 i=0; i<len; ++i) {
		switch (string[i]) {
			case '0':
				out[(i<<2)+0] = 0;
				out[(i<<2)+1] = 0;
				out[(i<<2)+2] = 0;
				out[(i<<2)+3] = 0;
				break;
			case '1':
				out[(i<<2)+0] = 0;
				out[(i<<2)+1] = 0;
				out[(i<<2)+2] = 0;
				out[(i<<2)+3] = 1;
				break;
			case '2':
				out[(i<<2)+0] = 0;
				out[(i<<2)+1] = 0;
				out[(i<<2)+2] = 1;
				out[(i<<2)+3] = 0;
				break;
			case '3':
				out[(i<<2)+0] = 0;
				out[(i<<2)+1] = 0;
				out[(i<<2)+2] = 1;
				out[(i<<2)+3] = 1;
				break;
			case '4':
				out[(i<<2)+0] = 0;
				out[(i<<2)+1] = 1;
				out[(i<<2)+2] = 0;
				out[(i<<2)+3] = 0;
				break;
			case '5':
				out[(i<<2)+0] = 0;
				out[(i<<2)+1] = 1;
				out[(i<<2)+2] = 0;
				out[(i<<2)+3] = 1;
				break;
			case '6':
				out[(i<<2)+0] = 0;
				out[(i<<2)+1] = 1;
				out[(i<<2)+2] = 1;
				out[(i<<2)+3] = 0;
				break;
			case '7':
				out[(i<<2)+0] = 0;
				out[(i<<2)+1] = 1;
				out[(i<<2)+2] = 1;
				out[(i<<2)+3] = 1;
				break;
			case '8':
				out[(i<<2)+0] = 1;
				out[(i<<2)+1] = 0;
				out[(i<<2)+2] = 0;
				out[(i<<2)+3] = 0;
				break;
			case '9':
				out[(i<<2)+0] = 1;
				out[(i<<2)+1] = 0;
				out[(i<<2)+2] = 0;
				out[(i<<2)+3] = 1;
				break;
			case 'A':
				out[(i<<2)+0] = 1;
				out[(i<<2)+1] = 0;
				out[(i<<2)+2] = 1;
				out[(i<<2)+3] = 0;
				break;
			case 'B':
				out[(i<<2)+0] = 1;
				out[(i<<2)+1] = 0;
				out[(i<<2)+2] = 1;
				out[(i<<2)+3] = 1;
				break;
			case 'C':
				out[(i<<2)+0] = 1;
				out[(i<<2)+1] = 1;
				out[(i<<2)+2] = 0;
				out[(i<<2)+3] = 0;
				break;
			case 'D':
				out[(i<<2)+0] = 1;
				out[(i<<2)+1] = 1;
				out[(i<<2)+2] = 0;
				out[(i<<2)+3] = 1;
				break;
			case 'E':
				out[(i<<2)+0] = 1;
				out[(i<<2)+1] = 1;
				out[(i<<2)+2] = 1;
				out[(i<<2)+3] = 0;
				break;
			case 'F':
				out[(i<<2)+0] = 1;
				out[(i<<2)+1] = 1;
				out[(i<<2)+2] = 1;
				out[(i<<2)+3] = 1;
				break;
		}
	}
}

// ---------------------------------------------------------------------------
std::string Graphics::addGlyphs(std::string s1, std::string s2)
// ---------------------------------------------------------------------------
{
	std::string out;
	u8 d1 = s1.size() >> 1;
	u8 d2 = s2.size() >> 1;
	
	if (d2 == 32) {
		s2 = glyph64(s2);
	}
	
	for (u8 i=0; i<2; ++i) {
		out.append(s1.substr(i*d1, d1));
		out.append(s2.substr(i*d2, d2));
	}
	return out;
}

// ---------------------------------------------------------------------------
std::string Graphics::glyph64(std::string g)
// ---------------------------------------------------------------------------
{
	std::string out;
	// upper-left
	out.append(g.substr(0, 2));
	out.append(g.substr(4, 2));
	out.append(g.substr(8, 2));
	out.append(g.substr(12, 2));
	out.append(g.substr(16, 2));
	out.append(g.substr(20, 2));
	out.append(g.substr(24, 2));
	out.append(g.substr(28, 2));
	// upper-right
	out.append(g.substr(2, 2));
	out.append(g.substr(6, 2));
	out.append(g.substr(10, 2));
	out.append(g.substr(14, 2));
	out.append(g.substr(18, 2));
	out.append(g.substr(22, 2));
	out.append(g.substr(26, 2));
	out.append(g.substr(30, 2));
	// bottom-left
	out.append(g.substr(32, 2));
	out.append(g.substr(36, 2));
	out.append(g.substr(40, 2));
	out.append(g.substr(44, 2));
	out.append(g.substr(48, 2));
	out.append(g.substr(52, 2));
	out.append(g.substr(56, 2));
	out.append(g.substr(60, 2));
	// bottom-right
	out.append(g.substr(34, 2));
	out.append(g.substr(38, 2));
	out.append(g.substr(42, 2));
	out.append(g.substr(46, 2));
	out.append(g.substr(50, 2));
	out.append(g.substr(54, 2));
	out.append(g.substr(58, 2));
	out.append(g.substr(62, 2));
	return out;
}

